package com.ysw.experience.缓存击穿_穿透;

import com.ysw.spring.spring_util.json.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * @ClassName UserService
 * @Description 模拟业务类
 * @Author ysw
 * @Date 2022/5/12 0:28
 */
@Service
public class UserService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedisUtil redisUtil;

    public User getUserById(Long id) {
        String redisKey = RedisKeyConstant.PREFFIX_KEY + id;
        User user = redisUtil.queryWithPassThrough(redisKey, id, User.class, 20L, TimeUnit.SECONDS, this::getUser);
        return user;
    }

    public User getUserById_2(Long id) {
        String redisKey = RedisKeyConstant.PREFFIX_KEY + id;
        return redisUtil.queryWithPenetration(redisKey, id, User.class, TimeUnit.SECONDS, 10L,
                this::getUser,
                true);
    }


    ExecutorService executorService = Executors.newFixedThreadPool(3);
    public User getUserById_3(Long id) {
        String redisKey = RedisKeyConstant.PREFFIX_KEY + id;
        BoundValueOperations<String, String> boundValueOps = stringRedisTemplate.boundValueOps(redisKey);
        String jsonValue = boundValueOps.get();
        // 缓存未命中，直接返回,因为是热点数据，所以就不用去考虑其他问题，
        // 缓存如果没有，那么他就是不在活动范围内的数据
        if (StringUtils.isBlank(jsonValue)) {
            return null;
        }
        LogicExpireData logicExpireData = JsonUtils.stringToObject(jsonValue, LogicExpireData.class);
        long now = System.currentTimeMillis();
        Long expireTime = logicExpireData.getExpireTime();
        // 说明已经过期了
        if (expireTime <= now) {
            RLock lock = null;
            try {
                lock = redissonClient.getLock(RedisKeyConstant.REDISSON_LOGIC_PREFIX + id);
                boolean isLock = lock.tryLock(0, 20, TimeUnit.SECONDS);
                if(isLock){
                    RLock finalLock = lock;
                    Runnable task = () -> {
                        try {
                            long newExpireTime = now + TimeUnit.MILLISECONDS.convert(10, TimeUnit.SECONDS);
                            User user = getUser(id);
                            LogicExpireData newData = new LogicExpireData().setData(user).setExpireTime(newExpireTime);
                            boundValueOps.set(JsonUtils.objectToString(newData));
                        } finally {
                            if (null != finalLock && finalLock.isHeldByCurrentThread()) {
                                finalLock.unlock();
                            }
                        }
                    };
                    executorService.submit(task);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return (User) logicExpireData.getData();
    }


    private User lockWithPassThrow(Long id, Boolean isCheckPathThrow) {
        String redisKey = RedisKeyConstant.PREFFIX_KEY + id;
        String jsonValue = stringRedisTemplate.opsForValue().get(redisKey);
        // 缓存命中,存在缓存数据
        if (StringUtils.isNotBlank(jsonValue)) {
            return JsonUtils.stringToObject(jsonValue, User.class);
        }
        // 解决缓存穿透
        if (null != jsonValue && isCheckPathThrow) {
            return null;
        }
        User user = getUser(id);
        // 解决缓存穿透，赋空值
        if (null == user && isCheckPathThrow) {
            stringRedisTemplate.opsForValue().set(redisKey, "");
            return null;
        }
        RLock lock = null;
        try {
            lock = redissonClient.getLock(RedisKeyConstant.PREFFIX_KEY + RedisKeyConstant.PREFFIX_KEY + id);
            boolean isLock = lock.tryLock();
            if (isLock) {
                String doubleCheckValue = stringRedisTemplate.opsForValue().get(redisKey);
                // 缓存命中,存在缓存数据
                if (StringUtils.isNotBlank(doubleCheckValue)) {
                    return JsonUtils.stringToObject(doubleCheckValue, User.class);
                }
                stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.objectToString(user));
                return user;
            } else {
                TimeUnit.MILLISECONDS.sleep(50);
                // 进行重试
                lockWithPassThrow(id, false);
            }
        } catch (Exception e) {
            return null;
        } finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
        return user;
    }


    /**
     * 模拟连接数据库操作
     *
     * @param id id
     * @return
     */
    User getUser(Long id) {
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new User(id, "测试缓存问题");
    }
}